package com.tangsm.api.demo.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tangsm.api.demo.annotation.ResponseResultBody;
import com.tangsm.api.demo.enums.ResultStatus;
import com.tangsm.api.demo.result.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.WebUtils;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Locale;

/**
 * 当类和方法使用了@ResponseResultBody,  放接口的返回进行统一处理
 * <p>
 * 对异常请求进行统一的处理
 *
 * @author 湯神码
 * @date 2020/10/20
 */
@RestControllerAdvice
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;
    private final Logger logger = LoggerFactory.getLogger(ResponseResultBodyAdvice.class);
    @Autowired
    private MessageSource messageSource;

    /**
     * 判断类或者方法是否使用了 @ResponseResultBody
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(),
                ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 当类或者方法使用了 @ResponseResultBody 就会调用这个方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        }
        //处理返回值是String的情况,此方案不是很好
        if (body instanceof String) {
            ObjectMapper mapper = new ObjectMapper();
            try {
                return mapper.writeValueAsString(Result.success(body));
            } catch (JsonProcessingException e) {
                logger.error("Json 转换异常", e);
            }
        }
        return Result.success(body);
    }

    /**
     * 提供对标准Spring MVC异常的处理
     *
     * @param ex      the target exception
     * @param request the current request
     */
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Result<?>> exceptionHandler(Exception ex, WebRequest request) {
        logger.error("ExceptionHandler: {}", ex.getMessage(), ex);

        // 返回指定Header
        HttpHeaders headers = new HttpHeaders();

        // TODO: 2020/11/13 tangsm 这里可以自定义其他的异常拦截
        // 判断是否参数验证异常
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException e = (MethodArgumentNotValidException) ex;

            // 获取验证失败的返回结果
            BindingResult result = e.getBindingResult();

            // 验证不通过
            if (result.hasErrors()) {
                StringBuffer message = new StringBuffer();
                message.append("参数有误");
                //获取错误字段集合
                List<FieldError> fieldErrors = result.getFieldErrors();
                //获取本地locale,zh_CN
                Locale currentLocale = LocaleContextHolder.getLocale();
                //遍历错误字段获取错误消息
                for (FieldError fieldError : fieldErrors) {
                    //获取错误信息
                    String errorMessage = messageSource.getMessage(fieldError, currentLocale);
                    //添加到错误消息集合内
                    message.append(String.format(",%s:%s", fieldError.getField(), errorMessage));
                }
                logger.info("MethodArgumentNotValidException 异常, 异常信息:{}", message.toString());
                return this.handleValidException(message.toString(), ex, headers, request);
            }
        }
        return this.handleException(ex, headers, request);
    }

    /**
     * 异常类的参数校验处理
     *
     * @param message 错误信息描述
     * @param ex      异常信息
     * @param headers http headers
     * @param request 请求的通用接口
     * @return
     */
    protected ResponseEntity<Result<?>> handleValidException(String message, Exception ex, HttpHeaders headers, WebRequest request) {
        Result<?> body = Result.failure(message);

        HttpStatus status = ResultStatus.BAD_REQUEST.getHttpStatus();

        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * 异常类的统一处理
     */
    protected ResponseEntity<Result<?>> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
        Result<?> body = Result.failure();
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleExceptionInternal(java.lang.Exception, java.lang.Object, org.springframework.http.HttpHeaders, org.springframework.http.HttpStatus, org.springframework.web.context.request.WebRequest)
     * <p>
     * A single place to customize the response body of all exception types.
     * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
     * request attribute and creates a {@link ResponseEntity} from the given
     * body, headers, and status.
     */
    protected ResponseEntity<Result<?>> handleExceptionInternal(
            Exception ex, Result<?> body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        return new ResponseEntity<>(body, headers, status);
    }
}